@tool
class_name State
extends Node

#region 属性
# 当前状态是否处于活动状态
var active:bool = false
var pending_transition:Transition = null
var pending_transition_remaining_delay:float = 0
var pending_transition_init_delay:float = 0
var transitions:Array[Transition] = []
var chart:StateChart
var get_state_status_func:Callable
## 开始时间
var enter_tick:int = -1
## 退出时间
var exit_tick:int = -1
#endregion
#region 字段
#endregion
#region 信号
# 状态进入
signal state_entered()
# 状态退出
signal state_exited()
# 收到event，仅当State活跃时调用
signal event_received(event:StringName)
# 状态更新
signal state_processing(delta:float)
# 状态物理更新
signal state_physics_processing(delta:float)
# StateChart.step()调用时
signal state_stepped()
# 状态收到Input
signal state_input(event:InputEvent)
# 状态收到没有处理的Input
signal state_unhandled_input(event:InputEvent)
# 状态等待延迟转换时每帧调用一次
signal transition_pending(init_delay:float,remaining_delay:float)
#endregion

#region 基类方法
#endregion
#region 公共方法
## 初始化
func init():
	process_mode = Node.PROCESS_MODE_DISABLED
	active = false
	enter_tick = -1
	exit_tick = -1
	_toggle_processing(false)
	transitions.clear()
	for child in get_children():
		if child is Transition:
			transitions.append(child)
## enter
func enter(expect_transition:bool = false):
	active = true
	enter_tick = Time.get_ticks_msec()
	exit_tick = -1
	process_mode = Node.PROCESS_MODE_INHERIT
	_toggle_processing(true)
	state_entered.emit()
	for transition in transitions:
		if not transition.has_event and transition.evaluate_guard():
			_run_transition(transition)
			break
# exit
func exit():
	pending_transition = null
	pending_transition_init_delay = 0
	pending_transition_remaining_delay = 0
	active = false
	enter_tick = -1
	exit_tick = Time.get_ticks_msec()
	process_mode = Node.PROCESS_MODE_DISABLED
	_toggle_processing(false)
	state_exited.emit()
# save
# 当State应该被Save时调用。
# 参数是父状态的SavedState对象。
# 该状态应以自己的名称向SavedState对象添加一个子对象。
# child_levels参数指示应保存多少级别的子级。如果设置为-1（默认值），则应保存所有子项。如果设置为0，则不应保存任何孩子。
# 只有当状态处于活动状态时，才会调用此方法，并且只有当孩子应该处于活动状态的时候，才应该对活动的孩子调用此方法
func save(saved_state:SavedState,child_levels:int = -1):
	if not active:
		push_error("save只会在活动State上调用")
		return
	var our_saved_state = SavedState.new()
	our_saved_state.pending_transition_name = pending_transition.name if pending_transition != null else ""
	our_saved_state.pending_transition_init_delay = pending_transition_remaining_delay
	our_saved_state.pending_transition_remaning_delay = pending_transition_remaining_delay
	our_saved_state.add_substate(self,our_saved_state)
	if child_levels == 0:
		return
	for child in get_children():
		if child is State and child.active:
			child.save(our_saved_state,child_levels if child_levels == -1 else child_levels - 1)
# restore
# 当State应该恢复时调用。
# 该参数是父状态的SavedState对象。
# 预期状态将从父级检索其自身的SavedState对象，并从中还原其状态。
# child_levels参数指示应还原多少级别的子级。如果设置为-1（默认值），则应恢复所有子项。如果设置为0，则不应恢复任何子项。
# 如果状态在保存时未处于活动状态，则仍将调用此方法，但给定的SavedState对象将不包含此状态的任何数据。
func restore(saved_state:SavedState,child_levels:int = -1):
	var our_saved_state = saved_state.get_substate_or_null(self)
	if our_saved_state == null:
		if active:
			exit()
		return
	if not active:
		enter()
	pending_transition = get_node_or_null(our_saved_state.pending_transition_name) as Transition
	pending_transition_init_delay = our_saved_state.pending_transition_init_delay
	pending_transition_remaining_delay = our_saved_state.pending_transition_remaning_delay
	if child_levels == 0:
		return
	for child in get_children():
		if child is State:
			child.restore(our_saved_state,child_levels if child_levels == -1 else child_levels - 1)
func handle_transition(transition:Transition,source:State):
	push_error("State {0} 没有handle_transition".format([name]))
func step():
	state_stepped.emit()
## 处理所有转换，如果property_change为true，只处理没有event的转换（无event转换和自动转换）
func process_transitions(event:StringName,property_change:bool = false) -> bool:
	if not active:
		return false
	if not property_change:
		event_received.emit(event)
	for transition in transitions:
		if transition != pending_transition and (not transition.has_event or (not property_change and transition.event == event)) and transition.evaluate_guard():
			_run_transition(transition)
			return true
	return false
## 判断能否转换
func get_state_status() -> GameEnum.StateStatus:
	return get_state_status_func.call() if get_state_status_func.is_valid() else GameEnum.StateStatus.Ready
#endregion
#region 私有方法
## 查找StartChart
func _find_chart() -> StateChart:
	var parent = get_parent()
	while parent:
		if parent is StateChart:
			return parent
		parent = parent.get_parent()
	return parent
func _run_transition(transition:Transition):
	var init_delay = transition.evaluate_delay()
	if init_delay > 0:
		_queue_transition(transition,init_delay)
	else:
		chart._run_transition(transition,self)
func _queue_transition(transition:Transition,init_delay:float):
	pending_transition = transition
	pending_transition_init_delay = init_delay
	pending_transition_remaining_delay = pending_transition_init_delay
	set_process(true)
func _toggle_processing(active:bool):
	set_process(active and _has_connections(state_processing))
	set_physics_process(active and _has_connections(state_physics_processing))
	set_process_input(active and _has_connections(state_input))
	set_process_unhandled_input(active and _has_connections(state_unhandled_input))
func _has_connections(si:Signal) -> bool:
	return si.get_connections().size() > 0
#endregion
#region 生命周期函数
func _ready() -> void:
	if Engine.is_editor_hint():
		return
	chart = _find_chart()
func _process(delta: float) -> void:
	if Engine.is_editor_hint():
		return
	state_processing.emit(delta)
	# 延迟调用转换条件检查函数，保证其它逻辑都处理完了再处理转换判断
	var transition_callable = func():
		if pending_transition != null:
			pending_transition_remaining_delay -= delta
			transition_pending.emit(pending_transition.delay_seconds,max(0,pending_transition_remaining_delay))
			if pending_transition_remaining_delay <= 0:
				var transition_to_send = pending_transition
				pending_transition = null
				pending_transition_remaining_delay = 0
				chart._run_transition(transition_to_send,self)
	transition_callable.call_deferred()
func _physics_process(delta: float) -> void:
	if Engine.is_editor_hint():
		return
	state_physics_processing.emit(delta)
func _input(event: InputEvent) -> void:
	state_input.emit(event)
func _unhandled_input(event: InputEvent) -> void:
	state_unhandled_input.emit(event)
func _get_configuration_warnings() -> PackedStringArray:
	var warning:Array = []
	var chart = _find_chart()
	if not chart or chart is not StateChart:
		warning.append("State必须是StateChart的子节点，否则无法工作")
	return warning
#endregion
